package org.JWCB.gateway.filter;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.JWCB.common.core.constants.UserConstants;
import org.JWCB.common.core.utils.ThreadLocalUtil;
import org.JWCB.gateway.config.IgnoreWhiteProperties;
import org.JWCB.common.core.constants.CacheConstants;
import org.JWCB.common.core.constants.HttpConstants;
import org.JWCB.common.core.domain.entity.LoginUser;
import org.JWCB.common.core.domain.vo.R;
import org.JWCB.common.core.enums.ResultCode;
import org.JWCB.common.core.enums.UserIdentity;
import org.JWCB.common.redis.service.RedisService;
import org.JWCB.common.core.utils.JWTUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;

import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;


@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered {
    @Autowired
    private IgnoreWhiteProperties ignoreWhite;

    @Value("${jwt.secret}")
    private String secret;

    @Autowired
    private RedisService redisService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        String url = request.getURI().getPath();
        // 跳过不需要验证的路径
        if (matches(url,ignoreWhite.getWhites())) {
            return chain.filter(exchange);
        }

        // 对token进行校验
        String token = getToken(request);
        if(StrUtil.isEmpty(token)) {
            return unauthorizedResponse(exchange,"令牌不能为空");
        }
        Claims claims;
        try {
            claims = JWTUtils.parseToken(token,secret);
            if(claims == null) {
                return unauthorizedResponse(exchange,"令牌已过期或验证不正确!");
            }
        }catch (Exception e) {
            return unauthorizedResponse(exchange,"令牌已过期或令验证不正确!");
        }
        String userKey = JWTUtils.getUserKey(claims);
        String userId = JWTUtils.getUserId(claims);
        if(StrUtil.isEmpty(userId) || StrUtil.isEmpty(userKey)) {
            return unauthorizedResponse(exchange,"令牌校验失败");
        }
        boolean isLogin = redisService.hasKey(getTokenKey(userKey));
        if(!isLogin) {
            return unauthorizedResponse(exchange,"登录状态已过期");
        }
        LoginUser loginUser = redisService.getCacheObject(getTokenKey(userKey),LoginUser.class);
        if(url.contains(HttpConstants.SYSTEM_URL_PREFIX) && !UserIdentity.ADMIN.getValue().equals(loginUser.getIdentityCode())) {
            return unauthorizedResponse(exchange,"权限不足");
        }
        if(url.contains(HttpConstants.FRIEND_URL_PREFIX) && !UserIdentity.ORDINARY.getValue().equals(loginUser.getIdentityCode())) {
            return unauthorizedResponse(exchange,"权限不足");
        }


        return chain.filter(exchange);
    }

    /**
     * @param userKey 唯一标识
     * @return redis中的key
     */
    private String getTokenKey(String userKey) {
        return CacheConstants.LOGIN_TOKEN_KEY + userKey;
    }


    /**
     * 从请求头中获取token
     * @param request 请求
     * @return token
     */
    private String getToken(ServerHttpRequest request) {
        String token = request.getHeaders().getFirst(HttpConstants.AUTHENTICATION);
        if(StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.TOKEN_PREFIX)) {
            token = token.replaceFirst(HttpConstants.TOKEN_PREFIX,StrUtil.EMPTY);
        }
        return token;
    }


    /**
     * 验证路径是否在白名单内
     * @param url 路径
     * @param whites 白名单
     * @return true 是 false 不是
     */
    private boolean matches(String url, List<String> whites) {
        if(StrUtil.isEmpty(url) || CollectionUtils.isEmpty(whites)) {
            return false;
        }
        for(String pattern : whites) {
            if(isMatch(pattern,url)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断url是否与规则匹配
     * @param pattern 匹配规则
     * @param url 需要匹配的url
     * @return true 匹配 false 不匹配
     */
    private boolean isMatch(String pattern, String url) {
        AntPathMatcher matcher = new AntPathMatcher();
        return matcher.match(pattern,url);
    }


    /**
     * @param exchange 请求信息
     * @param msg 状态信息
     * @return
     */
    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return webFluxResponseWriter(exchange.getResponse(),msg, ResultCode.FAILED_UNAUTHORIZED.getCode());
    }

    /**
     * 拼装webflux模型响应
     * @param response 响应
     * @param msg 状态信息
     * @param code 状态码
     * @return webflux模型响应
     */
    private Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String msg, int code) {
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE,
                MediaType.APPLICATION_JSON_VALUE);
        R<?> result = R.fail(code,msg);
        DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
        return response.writeWith(Mono.just(dataBuffer));
    }


    @Override
    public int getOrder() {
        return -200;
    }
}
